#include <ffi.h>
#include <dlfcn.h>
#include <stdint.h>
#include <stdlib.h>
#include "gc.h"
#include "mem.h"
#include "rmath.h"
#include "redobject.h"
#include "redintobject.h"
#include "redmapobject.h"
#include "redboolobject.h"
#include "rednullobject.h"
#include "redarrayobject.h"
#include "redfloatobject.h"
#include "redtupleobject.h"
#include "redstringobject.h"
#include "redexceptionobject.h"

/* FFI Struct */

typedef struct _FFI_Struct
{
	RedObject_HEAD

	char		  user;
	size_t		  size;
	ffi_type	 *type;
	RedObject	 *names;
	RedObject	 *fields;
	ffi_type	**elements;
} FFI_Struct;

#define STRUCT_User(o)			(((FFI_Struct *)(o))->user)
#define STRUCT_Size(o)			(((FFI_Struct *)(o))->size)
#define STRUCT_Type(o)			(((FFI_Struct *)(o))->type)
#define STRUCT_Names(o)			(((FFI_Struct *)(o))->names)
#define STRUCT_Fields(o)		(((FFI_Struct *)(o))->fields)
#define STRUCT_Elements(o)		(((FFI_Struct *)(o))->elements)

static char ffi_struct_init(RedObject *self, RedObject *args);

static void ffi_struct_clear(RedObject *self)
{
	Red_UNREF(STRUCT_Names(self));
	Red_UNREF(STRUCT_Fields(self));
}

static void ffi_struct_release(RedObject *self)
{
	/* free only when created by user */
	if (STRUCT_User(self))
	{
		RedMem_Free(STRUCT_Type(self));
		RedMem_Free(STRUCT_Elements(self));
	}

	ffi_struct_clear(self);
	Red_RELEASE();
}

static void ffi_struct_traverse(RedObject *self, tp_visit_fn_t visit, void *args)
{
	Red_VISIT(STRUCT_Names(self));
	Red_VISIT(STRUCT_Fields(self));
}

static RedObject *ffi_struct_dir(RedObject *self)
{
	RedObject *fields = STRUCT_Fields(self);
	RedObject *result = RedArray_CreatePresized(RedTuple_Length(fields));

	for (int i = 0; i < RedTuple_Length(fields); i++)
	{
		RedObject *item = RedTuple_Items(fields)[i];
		RedObject *fieldname = RedTuple_Items(item)[0];

		Red_REF(fieldname);
		RedArray_Items(result)[i] = fieldname;
	}

	RedObject_GC_Insert(result);
	return result;
}

static RedObject *ffi_struct_len(RedObject *self)
{
	return RedInt_FromInt(STRUCT_Size(self));
}

static RedObject *ffi_struct_invoke(RedObject *self, RedObject *args);

static RedTypeObject FFI_StructType =
{
	RedType_INIT("ffi_struct", FFI_Struct, RedType_Object)

	.tp_init		= ffi_struct_init,
	.tp_clear		= ffi_struct_clear,
	.tp_release		= ffi_struct_release,
	.tp_traverse	= ffi_struct_traverse,

	.tp_dir			= ffi_struct_dir,
	.tp_len			= ffi_struct_len,
	.tp_invoke		= ffi_struct_invoke,
};

static char ffi_struct_init(RedObject *self, RedObject *args)
{
	if (!Red_INIT())
		return 0;

	/* internal call for built-in FFI types */
	if (!args)
		return 1;

	/* we need to keep arg's reference cause it contains ffi_type instance */
	Red_REF(args);
	STRUCT_User(self) = 1;
	STRUCT_Size(self) = 0;
	STRUCT_Fields(self) = args;

	/* create an name map to check duplication of field names */
	if (!(STRUCT_Names(self) = RedMap_CreateStringKey()))
		return 0;

	/* check arguments */
	for (int i = 0; i < RedTuple_Length(args); i++)
	{
		RedObject *name;
		RedObject *type;

		/* parse field tuple */
		if (!RedTuple_Unpack(RedTuple_Items(args)[i], "s#T", &name, &FFI_StructType, &type))
			return 0;

		/* field name duplicated */
		if (RedMap_HasKey(STRUCT_Names(self), name))
		{
			RedException_ThrowFormat(&RedExc_ValueError, "Duplicated field name '%s'", RedString_Buffer(name));
			return 0;
		}

		/* `void` type field is non-sense */
		if (STRUCT_Type(type)->type == FFI_TYPE_VOID)
		{
			RedException_ThrowFormat(&RedExc_TypeError, "Struct elements cannot be `void`");
			return 0;
		}

		/* calculate struct size */
		STRUCT_Size(self) += STRUCT_Size(type);
		RedMap_PutString(STRUCT_Names(self), name, Red_Null);
	}

	/* allocate memory for user-defined type */
	if (!(STRUCT_Type(self) = RedMem_Alloc(sizeof(ffi_type))))
		return 0;

	/* allocate memory for type fields */
	if (!(STRUCT_Elements(self) = RedMem_Alloc(sizeof(ffi_type *) * (RedTuple_Length(args) + 1))))
		return 0;

	/* initialize type */
	STRUCT_Type(self)->size = 0;
	STRUCT_Type(self)->type = FFI_TYPE_STRUCT;
	STRUCT_Type(self)->elements = STRUCT_Elements(self);
	STRUCT_Type(self)->alignment = 0;

	/* elements must be NULL-terminated */
	STRUCT_Elements(self)[RedTuple_Length(args)] = NULL;

	/* copy elements into ffi type */
	for (int i = 0; i < RedTuple_Length(args); i++)
	{
		RedObject *field = RedTuple_Items(args)[i];
		STRUCT_Elements(self)[i] = STRUCT_Type(RedTuple_Items(field)[1]);
	}

	/* add field array into GC list */
	RedObject_GC_Insert(STRUCT_Fields(self));
	return 1;
}

static RedObject *FFI_LoadType(ffi_type *type, size_t size)
{
	RedObject *result = RedObject_Create(&FFI_StructType, NULL);

	if (!result)
		return NULL;

	STRUCT_User(result) = 0;
	STRUCT_Size(result) = size;
	STRUCT_Type(result) = type;
	STRUCT_Fields(result) = NULL;
	STRUCT_Elements(result) = NULL;
	return result;
}

/* FFI Memory */
/* !!! RedScript GC doesn't manage the memories alloc'ed by FFI_Memory */

typedef struct _FFI_Memory
{
	RedObject_HEAD

	size_t	 size;
	void	*memory;
	char	 can_free;
	char	 auto_release;
} FFI_Memory;

#define MEM_Size(o)				(((FFI_Memory *)(o))->size)
#define MEM_Memory(o)			(((FFI_Memory *)(o))->memory)
#define MEM_CanFree(o)			(((FFI_Memory *)(o))->can_free)
#define MEM_AutoRelease(o)		(((FFI_Memory *)(o))->auto_release)

static RedObject *ffi_memory_len(RedObject *self)
{
	return RedInt_FromInt(MEM_Size(self));
}

static uint32_t ffi_memory_hash(RedObject *self)
{
	return rmath_hash_i64((intptr_t)MEM_Memory(self));
}

static char ffi_memory_init(RedObject *self, RedObject *args)
{
	if (!Red_INIT())
		return 0;

	/* added or sub'ed with other pointers */
	if (!args)
	{
		MEM_CanFree(self) = 0;
		MEM_AutoRelease(self) = 0;
		return 1;
	}

	if (!RedTuple_Exact(args))
	{
		RedException_ThrowFormat(&RedExc_TypeError, "Arguments must be tuple");
		return 0;
	}

	if (!RedTuple_Length(args))
	{
		RedException_ThrowFormat(&RedExc_ArgumentError, "ffi.Memory() takes exactly 1 argument");
		return 0;
	}

	void *data;
	size_t size;
	RedObject *arg = RedTuple_Items(args)[0];

	if (RedInt_Exact(arg))
	{
		size = RedInt_Value(arg);
		data = RedMem_Alloc(size);
	}
	else if (RedString_Exact(arg))
	{
		size = RedString_Length(arg);
		data = RedMem_Alloc(size + 1); /* including zero-terminator */
		memcpy(data, RedString_Buffer(arg), size + 1);
	}
	else
	{
		RedException_ThrowFormat(&RedExc_TypeError, "ffi.Memory() only accepts `int` and `string`");
		return 0;
	}

	MEM_Size(self) = size;
	MEM_Memory(self) = data;
	MEM_CanFree(self) = 1;
	MEM_AutoRelease(self) = 1;
	return 1;
}

static void ffi_memory_release(RedObject *self)
{
	if (MEM_CanFree(self))
		if (MEM_AutoRelease(self))
			RedMem_Free(MEM_Memory(self));

	Red_RELEASE();
}

static RedObject *ffi_memory_eq(RedObject *self, RedObject *other);
static RedObject *ffi_memory_add(RedObject *self, RedObject *other);
static RedObject *ffi_memory_sub(RedObject *self, RedObject *other);

static RedObject *ffi_memory_free(RedObject *self, RedObject *args)
{
	if (!RedTuple_Unpack(args, ""))
		return NULL;

	if (!MEM_Memory(self))
	{
		RedException_ThrowFormat(&RedExc_IOError, "Memory already free'd");
		return NULL;
	}

	RedMem_Free(MEM_Memory(self));
	MEM_Memory(self) = NULL;
	Red_Return_Null();
}

static RedObject *ffi_memory_autorelease(RedObject *self, RedObject *args)
{
	char autorelease;

	if (!RedTuple_Unpack(args, "b*", &autorelease))
		return NULL;

	MEM_AutoRelease(self) = autorelease;
	Red_Return_Null();
}

static RedObject *ffi_memory_isautorelease(RedObject *self, RedObject *args)
{
	if (!RedTuple_Unpack(args, ""))
		return NULL;

	if (MEM_AutoRelease(self))
		Red_Return_True();
	else
		Red_Return_False();
}

static RedObject *ffi_memory_memset(RedObject *self, RedObject *args)
{
	size_t count;
	int64_t value;

	if (!RedTuple_Unpack(args, "i*i*", &value, &count))
		return NULL;

	if (!MEM_Memory(self))
	{
		RedException_ThrowFormat(&RedExc_IOError, "Memory already free'd");
		return NULL;
	}

	if (count > MEM_Size(self))
	{
		RedException_ThrowFormat(&RedExc_IOError, "Access violation (address overflow)");
		return NULL;
	}

	memset(MEM_Memory(self), value, count);
	Red_Return_Null();
}

static RedObject *ffi_memory_memcpy(RedObject *self, RedObject *args);
static RedObject *ffi_memory_memmove(RedObject *self, RedObject *args);

static RedObject *ffi_memory_tonumber(RedObject *self, RedObject *args)
{
	RedObject *type;

	if (!RedTuple_Unpack(args, "T", &FFI_StructType, &type))
		return NULL;

	if (MEM_Size(self) < STRUCT_Size(args))
	{
		RedException_ThrowFormat(&RedExc_IOError, "Access violation (number size mismatch)");
		return NULL;
	}

	switch (STRUCT_Type(type)->type)
	{
		case FFI_TYPE_FLOAT:		return RedFloat_FromFloat(*(float *)MEM_Memory(self));
		case FFI_TYPE_DOUBLE:		return RedFloat_FromFloat((float)(*(double *)MEM_Memory(self)));
		case FFI_TYPE_LONGDOUBLE:	return RedFloat_FromFloat((float)(*(long double *)MEM_Memory(self)));

		case FFI_TYPE_SINT8:		return RedInt_FromInt(*(int8_t *)MEM_Memory(self));
		case FFI_TYPE_SINT16:		return RedInt_FromInt(*(int16_t *)MEM_Memory(self));
		case FFI_TYPE_SINT32:		return RedInt_FromInt(*(int32_t *)MEM_Memory(self));

		case FFI_TYPE_UINT8:		return RedInt_FromInt(*(uint8_t *)MEM_Memory(self));
		case FFI_TYPE_UINT16:		return RedInt_FromInt(*(uint16_t *)MEM_Memory(self));
		case FFI_TYPE_UINT32:		return RedInt_FromInt(*(uint32_t *)MEM_Memory(self));

		case FFI_TYPE_SINT64:		/* unsigned int64 may overflow */
		case FFI_TYPE_UINT64:		return RedInt_FromInt(*(int64_t *)MEM_Memory(self));
	}

	RedException_ThrowFormat(&RedExc_ValueError, "memory.tonumber() accepts integer or float types only");
	return NULL;
}

static RedObject *ffi_memory_tostring(RedObject *self, RedObject *args)
{
	size_t size = MEM_Size(self);

	if (size != UINTPTR_MAX)
	{
		if (!RedTuple_Unpack(args, ""))
			return NULL;
	}
	else
	{
		if (!RedTuple_Unpack(args, "i*", &size))
			return NULL;
	}

	return RedString_FromBufferAndSize(MEM_Memory(self), (int)size);
}

static RedMethodDesc ffi_memory_methods[] =
{
	{ "free"			, ffi_memory_free			},
	{ "autorelease"		, ffi_memory_autorelease	},
	{ "isautorelease"	, ffi_memory_isautorelease	},

	{ "memset"			, ffi_memory_memset			},
	{ "memcpy"			, ffi_memory_memcpy			},
	{ "memmove"			, ffi_memory_memmove		},

	{ "tonumber"		, ffi_memory_tonumber		},
	{ "tostring"		, ffi_memory_tostring		},
	{ NULL, NULL },
};

static RedTypeObject FFI_MemoryType =
{
	RedType_INIT("ffi_memory", FFI_Memory, RedType_Object)

	.tp_len		= ffi_memory_len,
	.tp_hash	= ffi_memory_hash,
	.tp_init	= ffi_memory_init,
	.tp_release	= ffi_memory_release,

	.tp_eq		= ffi_memory_eq,
	.tp_add		= ffi_memory_add,
	.tp_sub		= ffi_memory_sub,

	.tp_methods	= ffi_memory_methods,
};

static RedObject *ffi_memory_eq(RedObject *self, RedObject *other)
{
	if ((other->ob_type == &FFI_MemoryType) &&
		(MEM_Memory(self) == MEM_Memory(other)))
		Red_Return_True();
	else
		Red_Return_False();
}

static RedObject *ffi_memory_add(RedObject *self, RedObject *other)
{
	if (!RedInt_Exact(other))
	{
		RedException_ThrowFormat(&RedExc_TypeError, "Memory objects can only be added with integers");
		return NULL;
	}

	if (RedInt_Value(other) > MEM_Size(self))
	{
		RedException_ThrowFormat(&RedExc_IndexError, "Offset out of range");
		return NULL;
	}

	RedObject *result = RedObject_Create(&FFI_MemoryType, NULL);

	if (!result)
		return NULL;

	MEM_Size(result) = MEM_Size(self) - RedInt_Value(other);
	MEM_Memory(result) = MEM_Memory(self) + RedInt_Value(other);
	return result;
}

static RedObject *ffi_memory_sub(RedObject *self, RedObject *other)
{
	if (RedInt_Exact(other))
	{
		RedObject *result = RedObject_Create(&FFI_MemoryType, NULL);

		if (!result)
			return NULL;

		MEM_Size(result) = MEM_Size(self) + RedInt_Value(other);
		MEM_Memory(result) = MEM_Memory(self) - RedInt_Value(other);
		return result;
	}
	else if (other->ob_type == &FFI_MemoryType)
	{
		/* pointer substracts with another pointer gets an integer */
		return RedInt_FromInt(MEM_Memory(self) - MEM_Memory(other));
	}
	else
	{
		RedException_ThrowFormat(&RedExc_TypeError, "Memory objects can only be sub'ed with integers or other memory objects");
		return NULL;
	}
}

static RedObject *ffi_memory_memmove(RedObject *self, RedObject *args)
{
	size_t count;
	RedObject *value;

	if (!RedTuple_Unpack(args, "Ti*", &FFI_MemoryType, &value, &count))
		return NULL;

	if (count > MEM_Size(self))
	{
		RedException_ThrowFormat(&RedExc_IOError, "Access violation (destination address overflow)");
		return NULL;
	}

	if (count > MEM_Size(value))
	{
		RedException_ThrowFormat(&RedExc_IOError, "Access violation (source address overflow)");
		return NULL;
	}

	memmove(MEM_Memory(self), MEM_Memory(value), count);
	Red_Return_Null();
}

/* FFI Instance */

typedef struct _FFI_Instance
{
	RedObject_HEAD

	RedObject	*mmap;
	RedObject	*types;
	void		*memory;
	char		 allocated;
} FFI_Instance;

#define INST_Mmap(o)			(((FFI_Instance *)(o))->mmap)
#define INST_Types(o)			(((FFI_Instance *)(o))->types)
#define INST_Memory(o)			(((FFI_Instance *)(o))->memory)
#define INST_Allocated(o)		(((FFI_Instance *)(o))->allocated)

static RedObject *ffi_instance_dir(RedObject *self)
{
	return RedObject_Dir(INST_Types(self));
}

static char ffi_instance_init(RedObject *self, RedObject *args)
{
	if (!Red_INIT())
		return 0;

	void *pointer = NULL;
	RedObject *types = NULL;

	if (!RedTuple_Exact(args))
	{
		/* mark as allocated */
		types = args;
		INST_Allocated(self) = 1;
	}
	else
	{
		/* constructing by script */
		switch (RedTuple_Length(args))
		{
			/* construct a brand new */
			case 1:
			{
				if (!RedTuple_Unpack(args, "T", &FFI_StructType, &types))
					return 0;

				/* mark as allocated */
				INST_Allocated(self) = 1;
				break;
			}

			/* construct from pointer */
			case 2:
			{
				RedObject *memory;

				if (!RedTuple_Unpack(args, "TT", &FFI_StructType, &types, &FFI_MemoryType, &memory))
					return 0;

				pointer = MEM_Memory(memory);
				INST_Allocated(self) = 0;
				break;
			}

			default:
			{
				RedException_ThrowFormat(&RedExc_ArgumentError, "Instance constructor takes 1 ~ 2 arguments");
				return 0;
			}
		}
	}

	if (INST_Allocated(self))
		if (!(pointer = RedMem_Alloc(STRUCT_Size(types))))
			return 0;

	Red_REF(types);
	INST_Mmap(self) = RedMap_CreateStringKey();
	INST_Types(self) = types;
	INST_Memory(self) = pointer;

	if (!STRUCT_User(types))
	{
		RedObject *name = RedString_FromCString("value");
		RedObject *memory = RedObject_Create(&FFI_MemoryType, NULL);

		if (!memory)
			return 0;

		MEM_Size(memory) = STRUCT_Size(types);
		MEM_Memory(memory) = pointer;

		RedObject *tuple = RedTuple_FromObjects(2, types, memory);

		RedMap_PutString(INST_Mmap(self), name, tuple);
		Red_UNREF(name);
		Red_UNREF(tuple);
		Red_UNREF(memory);
	}
	else
	{
		for (int i = 0; i < RedTuple_Length(STRUCT_Fields(types)); i++)
		{
			RedObject *name;
			RedObject *type;
			RedObject *field = RedTuple_Items(STRUCT_Fields(types))[i];

			if (!RedTuple_Unpack(field, "s#T", &name, &FFI_StructType, &type))
				return 0;

			RedObject *memory = RedObject_Create(&FFI_MemoryType, NULL);

			if (!memory)
				return 0;

			MEM_Size(memory) = STRUCT_Size(type);
			MEM_Memory(memory) = pointer;

			RedObject *tuple = RedTuple_FromObjects(2, type, memory);

			RedMap_PutString(INST_Mmap(self), name, tuple);
			Red_UNREF(tuple);
			Red_UNREF(memory);
			pointer += STRUCT_Size(type);
		}
	}

	return 1;
}

static void ffi_instance_clear(RedObject *self)
{
	Red_UNREF(INST_Mmap(self));
	Red_UNREF(INST_Types(self));
}

static void ffi_instance_release(RedObject *self)
{
	if (INST_Allocated(self))
		RedMem_Free(INST_Memory(self));

	ffi_instance_clear(self);
	Red_RELEASE();
}

static void ffi_instance_traverse(RedObject *self, tp_visit_fn_t visit, void *args)
{
	Red_VISIT(self->ob_attrs);
	Red_VISIT(INST_Mmap(self));
	Red_VISIT(INST_Types(self));
}

static RedObject *ffi_instance_delattr(RedObject *self, RedObject *attr)
{
	RedException_ThrowFormat(&RedExc_AttributeError, "Cannot __delattr__ from ffi.Struct objects");
	return NULL;
}

static RedObject *ffi_instance_getattr(RedObject *self, RedObject *attr);
static RedObject *ffi_instance_setattr(RedObject *self, RedObject *attr, RedObject *value);

static RedTypeObject FFI_InstanceType =
{
	RedType_Mutable_INIT("ffi_instance", FFI_Instance, RedType_Object)

	.tp_dir			= ffi_instance_dir,
	.tp_init		= ffi_instance_init,
	.tp_clear		= ffi_instance_clear,
	.tp_release		= ffi_instance_release,
	.tp_traverse	= ffi_instance_traverse,

	.tp_delattr		= ffi_instance_delattr,
	.tp_getattr		= ffi_instance_getattr,
	.tp_setattr		= ffi_instance_setattr,

	.tp_delitem		= ffi_instance_delattr,
	.tp_getitem		= ffi_instance_getattr,
	.tp_setitem		= ffi_instance_setattr,
};

static RedObject *ffi_struct_invoke(RedObject *self, RedObject *args)
{
	switch (RedTuple_Length(args))
	{
		/* construct a brand new instance */
		case 0:
			return RedObject_Create(&FFI_InstanceType, self);

		case 1:
		{
			RedObject *mem = RedTuple_Items(args)[0];
			RedObject *tuple = RedTuple_FromObjects(2, self, mem);
			RedObject *result = RedObject_Create(&FFI_InstanceType, tuple);

			Red_UNREF(tuple);
			return result;
		}

		default:
		{
			RedException_ThrowFormat(&RedExc_ArgumentError, "Constructor takes no arguments");
			return NULL;
		}
	}
}

static RedObject *ffi_memory_memcpy(RedObject *self, RedObject *args)
{
	size_t count;
	RedObject *value;

	if (!RedTuple_Unpack(args, "?i*", &value, &count))
		return NULL;

	if (count > MEM_Size(self))
	{
		RedException_ThrowFormat(&RedExc_IOError, "Access violation (destination address overflow)");
		return NULL;
	}

	void *p = NULL;
	size_t size = 0;

	if (RedString_Exact(value))
	{
		p = RedString_Buffer(value);
		size = RedString_Length(value);
	}
	else if (value->ob_type == &FFI_MemoryType)
	{
		p = MEM_Memory(value);
		size = MEM_Size(value);
	}
	else if (value->ob_type == &FFI_InstanceType)
	{
		p = INST_Memory(value);
		size = STRUCT_Size(INST_Types(value));
	}
	else
	{
		RedException_ThrowFormat(&RedExc_TypeError, "Can only memcpy from string or memory objects");
		return NULL;
	}

	if (count > size)
	{
		RedException_ThrowFormat(&RedExc_IOError, "Access violation (source address overflow)");
		return NULL;
	}

	memcpy(MEM_Memory(self), p, count);
	Red_Return_Null();
}

/* FFI Callback */

typedef struct _FFI_Callback
{
	RedObject_HEAD

	void			*boundfp;
	ffi_closure		*closure;
	RedObject		*callable;
	RedObject		*function;
} FFI_Callback;

#define CB_BoundFp(o)		(((FFI_Callback *)(o))->boundfp)
#define CB_Closure(o)		(((FFI_Callback *)(o))->closure)
#define CB_Callable(o)		(((FFI_Callback *)(o))->callable)
#define CB_Function(o)		(((FFI_Callback *)(o))->function)

static char ffi_callback_init(RedObject *self, RedObject *args);

static void ffi_callback_clear(RedObject *self)
{
	Red_UNREF(CB_Callable(self));
	Red_UNREF(CB_Function(self));
}

static void ffi_callback_release(RedObject *self)
{
	ffi_closure_free(CB_Closure(self));
	ffi_callback_clear(self);
	Red_RELEASE();
}

static void ffi_callback_traverse(RedObject *self, tp_visit_fn_t visit, void *args)
{
	Red_VISIT(CB_Callable(self));
	Red_VISIT(CB_Function(self));
}

static RedTypeObject FFI_CallbackType =
{
	RedType_INIT("ffi_callback", FFI_Callback, RedType_Object)

	.tp_init		= ffi_callback_init,
	.tp_clear		= ffi_callback_clear,
	.tp_release		= ffi_callback_release,
	.tp_traverse	= ffi_callback_traverse,
};

static RedObject *ffi_argument_boxing(RedObject *type, void *argument)
{
	/* wrap return value into RedObject */
	switch (STRUCT_Type(type)->type)
	{
		case FFI_TYPE_VOID:			Red_Return_Null();
		case FFI_TYPE_FLOAT:		return RedFloat_FromFloat(*(float *)argument);
		case FFI_TYPE_DOUBLE:		return RedFloat_FromFloat((float)(*(double *)argument));
		case FFI_TYPE_LONGDOUBLE:	return RedFloat_FromFloat((float)(*(long double *)argument));

		case FFI_TYPE_SINT8:		return RedInt_FromInt(*(int8_t *)argument);
		case FFI_TYPE_SINT16:		return RedInt_FromInt(*(int16_t *)argument);
		case FFI_TYPE_SINT32:		return RedInt_FromInt(*(int32_t *)argument);

		case FFI_TYPE_UINT8:		return RedInt_FromInt(*(uint8_t *)argument);
		case FFI_TYPE_UINT16:		return RedInt_FromInt(*(uint16_t *)argument);
		case FFI_TYPE_UINT32:		return RedInt_FromInt(*(uint32_t *)argument);

		case FFI_TYPE_SINT64:		/* unsigned int64 may overflow */
		case FFI_TYPE_UINT64:		return RedInt_FromInt(*(int64_t *)argument);

		case FFI_TYPE_POINTER:
		{
			/* returned pointer address */
			uintptr_t addr = *(uintptr_t *)argument;

			/* return RedNull_Null to represent `null` */
			if (!addr)
				Red_Return_Null();

			/* otherwise use a memory object to represent */
			RedObject *result = RedObject_Create(&FFI_MemoryType, NULL);

			if (!result)
				return NULL;

			MEM_Size(result) = UINTPTR_MAX;
			MEM_Memory(result) = (void *)addr;
			return result;
		}

		case FFI_TYPE_STRUCT:
		{
			/* create structure instance */
			RedObject *result = RedObject_Create(&FFI_InstanceType, type);

			/* memory allocation failed */
			if (!result)
				return NULL;

			/* copy struct into instance memory */
			memcpy(INST_Memory(result), argument, STRUCT_Size(type));
			return result;
		}
	}

	/* though impossible */
	RedException_ThrowFormat(&RedExc_InternalError, "Unknown ffi type");
	return NULL;
}

static char ffi_argument_unboxing(void *dest, ffi_type *type, RedObject *arg)
{
	switch (type->type)
	{
		case FFI_TYPE_VOID:
			return 1;

		case FFI_TYPE_FLOAT:
		case FFI_TYPE_DOUBLE:
		case FFI_TYPE_LONGDOUBLE:
		{
			if (!RedFloat_Exact(arg))
			{
				RedException_ThrowFormat(&RedExc_TypeError, "`float` expected when unboxing");
				return 0;
			}

			/* never memcpy directly, memory structure of each are incompatiable with others */
			switch (type->type)
			{
				case FFI_TYPE_FLOAT:		*(float			*)dest = RedFloat_Value(arg); break;
				case FFI_TYPE_DOUBLE:		*(double		*)dest = RedFloat_Value(arg); break;
				case FFI_TYPE_LONGDOUBLE:	*(long double	*)dest = RedFloat_Value(arg); break;
			}

			return 1;
		}

		case FFI_TYPE_UINT8:
		case FFI_TYPE_SINT8:
		case FFI_TYPE_UINT16:
		case FFI_TYPE_SINT16:
		case FFI_TYPE_UINT32:
		case FFI_TYPE_SINT32:
		case FFI_TYPE_UINT64:
		case FFI_TYPE_SINT64:
		{
			if (!RedInt_Exact(arg))
			{
				RedException_ThrowFormat(&RedExc_TypeError, "`int` expected when unboxing");
				return 0;
			}

			switch (type->type)
			{
				case FFI_TYPE_UINT8:
				{
					if (RedInt_Value(arg) >= 0 &&
						RedInt_Value(arg) <= 255)
					{
						*(uint8_t *)dest = (uint8_t)RedInt_Value(arg);
						break;
					}

					RedException_ThrowFormat(&RedExc_ValueError, "Integer out of range [0, 255]");
					return 0;
				}

				case FFI_TYPE_UINT16:
				{
					if (RedInt_Value(arg) >= 0 &&
						RedInt_Value(arg) <= 65535)
					{
						*(uint16_t *)dest = (uint16_t)RedInt_Value(arg);
						break;
					}

					RedException_ThrowFormat(&RedExc_ValueError, "Integer out of range [0, 65535]");
					return 0;
				}

				case FFI_TYPE_UINT32:
				{
					if (RedInt_Value(arg) >= 0 &&
						RedInt_Value(arg) <= 4294967295)
					{
						*(uint32_t *)dest = (uint32_t)RedInt_Value(arg);
						break;
					}

					RedException_ThrowFormat(&RedExc_ValueError, "Integer out of range [0, 4294967295]");
					return 0;
				}

					/* never overflows */
				case FFI_TYPE_UINT64:
				{
					if (RedInt_Value(arg) >= 0)
					{
						*(uint64_t *)dest = (uint64_t)RedInt_Value(arg);
						break;
					}

					RedException_ThrowFormat(&RedExc_ValueError, "Integer out of range [0, 9223372036854775807]");
					return 0;
				}

				case FFI_TYPE_SINT8:
				{
					if (RedInt_Value(arg) >= -128 &&
						RedInt_Value(arg) <= +127)
					{
						*(int8_t *)dest = (int8_t)RedInt_Value(arg);
						break;
					}

					RedException_ThrowFormat(&RedExc_ValueError, "Integer out of range [-128, 127]");
					return 0;
				}

				case FFI_TYPE_SINT16:
				{
					if (RedInt_Value(arg) >= -32768 &&
						RedInt_Value(arg) <= +32767)
					{
						*(int16_t *)dest = (int16_t)RedInt_Value(arg);
						break;
					}

					RedException_ThrowFormat(&RedExc_ValueError, "Integer out of range [-32768, 32767]");
					return 0;
				}

				case FFI_TYPE_SINT32:
				{
					if (RedInt_Value(arg) >= -2147483648 &&
						RedInt_Value(arg) <= +2147483647)
					{
						*(int32_t *)dest = (int32_t)RedInt_Value(arg);
						break;
					}

					RedException_ThrowFormat(&RedExc_ValueError, "Integer out of range [-2147483648, 2147483647]");
					return 0;
				}

					/* never overflows or underflows */
				case FFI_TYPE_SINT64:
				{
					*(int64_t *)dest = RedInt_Value(arg);
					break;
				}
			}

			return 1;
		}

		case FFI_TYPE_STRUCT:
		{
			if (arg->ob_type != &FFI_InstanceType)
			{
				RedException_ThrowFormat(&RedExc_TypeError, "`instance` expected unboxing");
				return 0;
			}

			if (STRUCT_Type(INST_Types(arg)) != type)
			{
				RedException_ThrowFormat(&RedExc_ValueError, "`instance` type mismatched argument");
				return 0;
			}

			memcpy(dest, INST_Memory(arg), STRUCT_Size(INST_Types(arg)));
			return 1;
		}
	}
	
	RedException_ThrowFormat(&RedExc_InternalError, "Unknown ffi type");
	return 0;
}

static RedObject *ffi_instance_getattr(RedObject *self, RedObject *attr)
{
	if (!RedString_Exact(attr))
	{
		RedException_ThrowFormat(&RedExc_TypeError, "Field name must be string");
		return NULL;
	}

	RedObject *desc = RedMap_GetString(INST_Mmap(self), attr);

	if (!desc)
	{
		RedException_ThrowFormat(&RedExc_AttributeError, "'%s' object doesn't have attribute '%s'", RedObject_ClassName(self), RedString_Buffer(attr));
		return NULL;
	}

	RedObject *type = RedTuple_Items(desc)[0];
	RedObject *memory = RedTuple_Items(desc)[1];
	return ffi_argument_boxing(type, MEM_Memory(memory));
}

static RedObject *ffi_instance_setattr(RedObject *self, RedObject *attr, RedObject *value)
{
	if (!RedString_Exact(attr))
	{
		RedException_ThrowFormat(&RedExc_TypeError, "Field name must be string");
		return NULL;
	}

	RedObject *desc = RedMap_GetString(INST_Mmap(self), attr);

	if (!desc)
	{
		RedException_ThrowFormat(&RedExc_AttributeError, "'%s' object doesn't have attribute '%s'", RedObject_ClassName(self), RedString_Buffer(attr));
		return NULL;
	}

	RedObject *type = RedTuple_Items(desc)[0];
	RedObject *memory = RedTuple_Items(desc)[1];

	switch (STRUCT_Type(type)->type)
	{
		case FFI_TYPE_FLOAT:
		case FFI_TYPE_DOUBLE:
		case FFI_TYPE_LONGDOUBLE:
		case FFI_TYPE_SINT8:
		case FFI_TYPE_SINT16:
		case FFI_TYPE_SINT32:
		case FFI_TYPE_SINT64:
		case FFI_TYPE_UINT8:
		case FFI_TYPE_UINT16:
		case FFI_TYPE_UINT32:
		case FFI_TYPE_UINT64:
		case FFI_TYPE_STRUCT:
		{
			if (ffi_argument_unboxing(MEM_Memory(memory), STRUCT_Type(type), value))
				break;

			/* exceptions already set by ffi_argument_unboxing */
			return NULL;
		}

		case FFI_TYPE_POINTER:
		{
			if (RedNull_IsNull(value))
			{
				/* null object represents NULL */
				*(void **)MEM_Memory(memory) = NULL;
			}
			else if (value->ob_type == &FFI_MemoryType)
			{
				if (!MEM_Memory(value))
				{
					RedException_ThrowFormat(&RedExc_IOError, "Access violation (memory already free'ed)");
					return NULL;
				}

				/* memory object can be loaded directly */
				*(void **)MEM_Memory(memory) = MEM_Memory(value);
			}
			else if (value->ob_type == &FFI_CallbackType)
			{
				/* callback object can be loaded directly */
				*(void **)MEM_Memory(memory) = CB_BoundFp(value);
			}
			else if (value->ob_type == &FFI_InstanceType)
			{
				/* struct object can be loaded directly */
				*(void **)MEM_Memory(memory) = INST_Memory(value);
			}
			else
			{
				RedException_ThrowFormat(&RedExc_TypeError, "`memory` or `instance` expected as field `%s`", RedString_Buffer(attr));
				return NULL;
			}

			break;
		}

		default:
		{
			RedException_ThrowFormat(&RedExc_TypeError, "Unknown struct field type");
			return NULL;
		}
	}
	
	Red_Return_Null();
}

/* FFI Function */

typedef struct _FFI_Function
{
	RedObject_HEAD

	void		 *ret;
	void		**args;
	void		 *func;
	RedObject	 *rtype;
	RedObject	 *types;
	ffi_type	**atypes;
	void		**abuffer;
	ffi_cif		  function;
} FFI_Function;

#define FUNC_Ret(o)			(((FFI_Function *)(o))->ret)
#define FUNC_Args(o)		(((FFI_Function *)(o))->args)
#define FUNC_Func(o)		(((FFI_Function *)(o))->func)
#define FUNC_RType(o)		(((FFI_Function *)(o))->rtype)
#define FUNC_Types(o)		(((FFI_Function *)(o))->types)
#define FUNC_ATypes(o)		(((FFI_Function *)(o))->atypes)
#define FUNC_ABuffer(o)		(((FFI_Function *)(o))->abuffer)
#define FUNC_Function(o)	(&(((FFI_Function *)(o))->function))

static char ffi_function_load(RedObject *self, void *func, RedObject *ret, RedObject *args)
{
	/* reference all argument types */
	Red_REF(ret);
	Red_REF(args);
	FUNC_Func(self) = func;
	FUNC_RType(self) = ret;
	FUNC_Types(self) = args;

	/* allocate result buffer */
	if (!(FUNC_Ret(self) = RedMem_Alloc(STRUCT_Size(ret))))
		return 0;

	/* allocate argument list */
	if (!(FUNC_Args(self) = RedMem_Alloc(sizeof(void *) * RedTuple_Length(args))))
		return 0;

	/* allocate FFI types buffer */
	if (!(FUNC_ATypes(self) = RedMem_Alloc(sizeof(ffi_type *) * RedTuple_Length(args))))
		return 0;

	/* arguments must be duplicated since the foreign functions may alter them */
	if (!(FUNC_ABuffer(self) = RedMem_Alloc(sizeof(void *) * RedTuple_Length(args))))
		return 0;

	/* clear abuffer first */
	for (int i = 0; i < RedTuple_Length(args); i++)
	{
		FUNC_ABuffer(self)[i] = NULL;

		/* check argument type by the way */
		if (RedTuple_Items(args)[i]->ob_type != &FFI_StructType)
		{
			RedException_ThrowFormat(&RedExc_TypeError, "Descriptor of arguments must be types");
			return 0;
		}
	}

	/* verify the return type */
	switch (STRUCT_Type(ret)->type)
	{
		case FFI_TYPE_VOID:
		case FFI_TYPE_POINTER:
		case FFI_TYPE_FLOAT:
		case FFI_TYPE_DOUBLE:
		case FFI_TYPE_LONGDOUBLE:
		case FFI_TYPE_SINT8:
		case FFI_TYPE_SINT16:
		case FFI_TYPE_SINT32:
		case FFI_TYPE_SINT64:
		case FFI_TYPE_UINT8:
		case FFI_TYPE_UINT16:
		case FFI_TYPE_UINT32:
		case FFI_TYPE_UINT64:
		case FFI_TYPE_STRUCT:
			break;

		default:
		{
			RedException_ThrowFormat(&RedExc_TypeError, "Unknown function return type");
			return 0;
		}
	}

	/* copy FFI type info */
	for (int i = 0; i < RedTuple_Length(args); i++)
	{
		FUNC_Args(self)[i] = NULL;
		FUNC_ATypes(self)[i] = STRUCT_Type(RedTuple_Items(args)[i]);

		switch (FUNC_ATypes(self)[i]->type)
		{
			case FFI_TYPE_POINTER:
				break;

			case FFI_TYPE_FLOAT:
			case FFI_TYPE_DOUBLE:
			case FFI_TYPE_LONGDOUBLE:
			case FFI_TYPE_SINT8:
			case FFI_TYPE_SINT16:
			case FFI_TYPE_SINT32:
			case FFI_TYPE_SINT64:
			case FFI_TYPE_UINT8:
			case FFI_TYPE_UINT16:
			case FFI_TYPE_UINT32:
			case FFI_TYPE_UINT64:
			case FFI_TYPE_STRUCT:
			{
				FUNC_Args(self)[i] = FUNC_ABuffer(self)[i] = RedMem_Alloc(STRUCT_Size(RedTuple_Items(args)[i]));
				break;
			}

			case FFI_TYPE_VOID:
			{
				Red_UNREF(self);
				RedException_ThrowFormat(&RedExc_ArgumentError, "Argument of type `void` is non-sense");
				return 0;
			}

			default:
			{
				Red_UNREF(self);
				RedException_ThrowFormat(&RedExc_TypeError, "Unknown argument type");
				return 0;
			}
		}
	}

	ffi_cif *cif = FUNC_Function(self);
	ffi_status status = FFI_OK;

	/* prepare CIF struct */
	if ((status = ffi_prep_cif(cif, FFI_DEFAULT_ABI, RedTuple_Length(args), STRUCT_Type(ret), FUNC_ATypes(self))) == FFI_OK)
		return 1;

	RedException_ThrowFormat(&RedExc_ArgumentError, "FFI error: %s", (status == FFI_BAD_ABI ? "Bad ABI" : "Bad typedef"));
	return 0;
}

static char ffi_function_init(RedObject *self, RedObject *args)
{
	if (!Red_INIT())
		return 0;

	/* internal construction */
	if (!args)
		return 1;

	RedObject *ret;
	RedObject *func;
	RedObject *types;

	/* unpack arguments descripted by `value` */
	if (!RedTuple_Unpack(args, "TTt", &FFI_MemoryType, &func, &FFI_StructType, &ret, &types))
		return 0;

	/* load arguments info into function object */
	return ffi_function_load(self, MEM_Memory(func), ret, args);
}

static void ffi_function_clear(RedObject *self)
{
	Red_UNREF(FUNC_RType(self));
	Red_UNREF(FUNC_Types(self));
}

static void ffi_function_release(RedObject *self)
{
	for (int i = 0; i < RedTuple_Length(FUNC_Types(self)); i++)
		RedMem_Free(FUNC_ABuffer(self)[i]);

	ffi_function_clear(self);
	RedMem_Free(FUNC_Ret(self));
	RedMem_Free(FUNC_Args(self));
	RedMem_Free(FUNC_ATypes(self));
	RedMem_Free(FUNC_ABuffer(self));
	Red_RELEASE();
}

static void ffi_function_traverse(RedObject *self, tp_visit_fn_t visit, void *args)
{
	Red_VISIT(FUNC_RType(self));
	Red_VISIT(FUNC_Types(self));
}

static RedObject *ffi_function_invoke(RedObject *self, RedObject *args)
{
	/* callbacks have no functino pointer, so it's not callable by RedScript */
	if (!FUNC_Func(self))
	{
		RedException_ThrowFormat(&RedExc_NotCallableError, "`ffi.Callback` is not callable by RedScript");
		return NULL;
	}

	/* types[0] is return type */
	int nargs = RedTuple_Length(FUNC_Types(self));

	/* check for arguments count */
	if (RedTuple_Length(args) != nargs)
	{
		RedException_ThrowFormat(&RedExc_ArgumentError, "Function takes exactly %d argument, but %d given", nargs, RedTuple_Length(args));
		return NULL;
	}

	/* convert and load all args into buffer */
	for (int i = 0; i < nargs; i++)
	{
		ffi_type *type = FUNC_ATypes(self)[i];
		RedObject *param = RedTuple_Items(args)[i];

		switch (type->type)
		{
			case FFI_TYPE_FLOAT:
			case FFI_TYPE_DOUBLE:
			case FFI_TYPE_LONGDOUBLE:
			case FFI_TYPE_SINT8:
			case FFI_TYPE_SINT16:
			case FFI_TYPE_SINT32:
			case FFI_TYPE_SINT64:
			case FFI_TYPE_UINT8:
			case FFI_TYPE_UINT16:
			case FFI_TYPE_UINT32:
			case FFI_TYPE_UINT64:
			case FFI_TYPE_STRUCT:
			{
				if (ffi_argument_unboxing(FUNC_ABuffer(self)[i], type, param))
					break;

				/* exceptions already set by ffi_argument_unboxing */
				return NULL;
			}

			case FFI_TYPE_POINTER:
			{
				if (RedNull_IsNull(param))
				{
					/* null objects represnts NULL */
					RedMem_Free(FUNC_ABuffer(self)[i]);
					FUNC_ABuffer(self)[i] = NULL;

					/* load string into ffi buffer */
					FUNC_Args(self)[i] = &(FUNC_ABuffer(self)[i]);
				}
				else if (RedString_Exact(param))
				{
					/* allocate enough space for string, including zero-terminator */
					FUNC_ABuffer(self)[i] = RedMem_Resize(FUNC_ABuffer(self)[i], RedString_Length(param) + 1);

					/* load string into ffi buffer */
					FUNC_Args(self)[i] = &(FUNC_ABuffer(self)[i]);
					memcpy(FUNC_ABuffer(self)[i], RedString_Buffer(param), RedString_Length(param) + 1);
				}
				else if (param->ob_type == &FFI_MemoryType)
				{
					if (!MEM_Memory(param))
					{
						RedException_ThrowFormat(&RedExc_IOError, "Access violation (memory already free'ed)");
						return NULL;
					}

					/* memory object can be loaded directly */
					FUNC_Args(self)[i] = &(MEM_Memory(param));
				}
				else if (param->ob_type == &FFI_CallbackType)
				{
					/* callback object can be loaded directly */
					FUNC_Args(self)[i] = &(CB_BoundFp(param));
				}
				else if (param->ob_type == &FFI_InstanceType)
				{
					/* struct object can be loaded directly */
					FUNC_Args(self)[i] = &(INST_Memory(param));
				}
				else
				{
					RedException_ThrowFormat(&RedExc_TypeError, "`string` `memory` or `instance` expected as it's %d-th argument", i);
					return NULL;
				}

				break;
			}
		}
	}

	/* invoke function using ffi */
	ffi_call(FUNC_Function(self), FUNC_Func(self), FUNC_Ret(self), FUNC_Args(self));

	/* convert function result into RedObjects */
	return ffi_argument_boxing(FUNC_RType(self), FUNC_Ret(self));
}

static RedTypeObject FFI_FunctionType =
{
	RedType_INIT("ffi_function", FFI_Function, RedType_Object)

	.tp_init		= ffi_function_init,
	.tp_clear		= ffi_function_clear,
	.tp_invoke		= ffi_function_invoke,
	.tp_release		= ffi_function_release,
	.tp_traverse	= ffi_function_traverse,
};

static void ffi_callback_wrap(ffi_cif *cif, void *ret, void **args, void *cb)
{
	/* extract callable descriptor */
	RedObject *callable = CB_Callable(cb);
	RedObject *function = CB_Function(cb);
	RedObject *arguments = RedTuple_FromLength(RedTuple_Length(FUNC_Types(function)));

	/* wrap C-style arguments into RedObjects */
	for (int i = 0; i < RedTuple_Length(arguments); i++)
	{
		/* convert argument */
		RedObject *type = RedTuple_Items(FUNC_Types(function))[i];
		RedObject *object = ffi_argument_boxing(type, args[i]);

		/* failed to convert */
		if (!object)
			return;

		/* add to argument tuple */
		RedTuple_Items(arguments)[i] = object;
	}

	/* invoke RedObject callable */
	RedObject *result = RedObject_Invoke(callable, arguments);

	/* check for exception */
	if (result)
	{
		/* unboxing into ret */
		ffi_argument_unboxing(ret, STRUCT_Type(FUNC_RType(function)), result);
		Red_UNREF(result);
		Red_UNREF(arguments);
	}
	else
	{
		/* no exception set, give it an internal exception */
		if (!RedException_Exception())
			RedException_ThrowFormat(&RedExc_InternalError, "Exception occured but no exceptions set");

		/* throw immediately */
		Red_UNREF(arguments);
		RedObject_Print(RedException_Exception());
		RedException_Clear();
	}
}

static char ffi_callback_init(RedObject *self, RedObject *args)
{
	if (!Red_INIT())
		return 0;

	RedObject *ret;
	RedObject *types;
	RedObject *callable;

	/* parse arguments */
	if (!RedTuple_Unpack(args, "CTt", &callable, &FFI_StructType, &ret, &types))
		return 0;

	/* keep the callable's reference */
	Red_REF(callable);
	CB_Callable(self) = callable;

	/* allocate closure object */
	if (!(CB_Closure(self) = ffi_closure_alloc(sizeof(ffi_closure), &CB_BoundFp(self))))
		return 0;

	/* create function object */
	if (!(CB_Function(self) = RedObject_Create(&FFI_FunctionType, NULL)))
		return 0;

	/* load function object */
	if (!ffi_function_load(CB_Function(self), NULL, ret, types))
		return 0;

	/* prepare closure */
	return ffi_prep_closure_loc(CB_Closure(self), FUNC_Function(CB_Function(self)), ffi_callback_wrap, self, CB_BoundFp(self)) == FFI_OK;
}


/* FFI Library */

typedef struct _FFI_Library
{
	RedObject_HEAD

	void	*lib;
} FFI_Library;

#define LIB_Lib(o)			(((FFI_Library *)(o))->lib)

static char ffi_library_init(RedObject *self, RedObject *args)
{
	if (!Red_INIT())
		return 0;

	char *name;

	/* parse argument tuples */
	if (!RedTuple_Unpack(args, "s*", &name))
		return 0;

	/* load this library */
	if ((LIB_Lib(self) = dlopen(name, RTLD_LAZY)))
		return 1;

	/* not loaded */
	RedException_ThrowErrno(&RedExc_IOError);
	return 0;
}

static void ffi_library_clear(RedObject *self)
{
	Red_UNREF(self->ob_attrs);
}

static void ffi_library_release(RedObject *self)
{
	ffi_library_clear(self);
	Red_RELEASE();
}

static void ffi_library_traverse(RedObject *self, tp_visit_fn_t visit, void *args)
{
	Red_VISIT(self->ob_attrs);
}

static RedObject *ffi_library_setattr(RedObject *self, RedObject *attr, RedObject *value)
{
	if (!RedString_Exact(attr))
	{
		RedException_ThrowFormat(&RedExc_TypeError, "Attribute name must be 'string'");
		return NULL;
	}

	/* cannot descript one function twice */
	RedObject *item = RedType_Object.tp_getattr(self, attr);

	if (item)
	{
		Red_UNREF(item);
		RedException_ThrowFormat(&RedExc_AttributeError, "Function already have a descriptor");
		return NULL;
	}

	/* `getattr` may throw an exception when no attrs found, but we don't want this */
	RedException_Clear();

	void *fp;
	RedObject *ret;
	RedObject *args;

	/* lookup function in library */
	if (!(fp = dlsym(LIB_Lib(self), RedString_Buffer(attr))))
	{
		RedException_ThrowFormat(&RedExc_IOError, "Library doesn't have function '%s'", RedString_Buffer(attr));
		return NULL;
	}

	/* unpack arguments descripted by `value` */
	if (!RedTuple_Unpack(value, "Tt", &FFI_StructType, &ret, &args))
		return NULL;

	/* create the foreign function object */
	RedObject *func = RedObject_Create(&FFI_FunctionType, NULL);

	/* memory allocation failed */
	if (!func)
		return NULL;

	/* load arguments info into function object */
	if (!ffi_function_load(func, fp, ret, args))
	{
		Red_UNREF(func);
		return NULL;
	}

	/* set into attributes map */
	RedObject *result = RedType_Object.tp_setattr(self, attr, func);

	Red_UNREF(func);
	return result;
}

static RedTypeObject FFI_LibraryType =
{
	/* Library objects are mutable */
	RedType_Mutable_INIT("ffi_library", FFI_Library, RedType_Object)

	.tp_init		= ffi_library_init,
	.tp_clear		= ffi_library_clear,
	.tp_release		= ffi_library_release,
	.tp_traverse	= ffi_library_traverse,
	.tp_setattr		= ffi_library_setattr,
};

/* module functions */

static RedObject *object_as_address(RedObject *object)
{
	return RedInt_FromInt((int64_t)object);
}

static RedObject *memory_as_address(RedObject *memory)
{
	if (memory->ob_type == &FFI_MemoryType)
		return RedInt_FromInt((int64_t)MEM_Memory(memory));

	RedException_ThrowFormat(&RedExc_TypeError, "`memory` object expected");
	return NULL;
}

static RedObject *memory_from_object(RedObject *object)
{
	RedObject *result = RedObject_Create(&FFI_MemoryType, NULL);

	if (!result)
		return NULL;

	MEM_Size(result) = object->ob_size;
	MEM_Memory(result) = (void *)object;
	return result;
}

static RedObject *memory_from_address(RedObject *address, RedObject *size)
{
	if (!RedInt_Exact(size))
	{
		RedException_ThrowFormat(&RedExc_TypeError, "`int` expected for `size` argument");
		return NULL;
	}

	if (!RedInt_Exact(address))
	{
		RedException_ThrowFormat(&RedExc_TypeError, "`int` expected for `address` argument");
		return NULL;
	}

	RedObject *result = RedObject_Create(&FFI_MemoryType, NULL);

	if (!result)
		return NULL;

	MEM_Size(result) = RedInt_Value(size);
	MEM_Memory(result) = (void *)RedInt_Value(address);
	return result;
}

/* Module entry */

static void module_exit(void *args)
{
	RedType_Cleanup(&FFI_StructType);
	RedType_Cleanup(&FFI_MemoryType);
	RedType_Cleanup(&FFI_LibraryType);
	RedType_Cleanup(&FFI_CallbackType);
	RedType_Cleanup(&FFI_FunctionType);
	RedType_Cleanup(&FFI_InstanceType);
}

RED_EXPORT char module_entry(RedObject *self, RedObject *name)
{
	if (!RedType_Prepare(&FFI_StructType))		return 0;
	if (!RedType_Prepare(&FFI_MemoryType))		return 0;
	if (!RedType_Prepare(&FFI_LibraryType))		return 0;
	if (!RedType_Prepare(&FFI_CallbackType))	return 0;
	if (!RedType_Prepare(&FFI_FunctionType))	return 0;
	if (!RedType_Prepare(&FFI_InstanceType))	return 0;

	RedType_Init(&FFI_StructType);
	RedType_Init(&FFI_MemoryType);
	RedType_Init(&FFI_LibraryType);
	RedType_Init(&FFI_CallbackType);
	RedType_Init(&FFI_FunctionType);
	RedType_Init(&FFI_InstanceType);

	/* set cleanup function */
	Red_ExitFunction(module_exit, NULL);

	/* types */
	if (!RedObject_AddConst(self, "Struct"	, (RedObject *)&FFI_StructType))	return 0;
	if (!RedObject_AddConst(self, "Memory"	, (RedObject *)&FFI_MemoryType))	return 0;
	if (!RedObject_AddConst(self, "Library"	, (RedObject *)&FFI_LibraryType))	return 0;
	if (!RedObject_AddConst(self, "Callback", (RedObject *)&FFI_CallbackType))	return 0;
	if (!RedObject_AddConst(self, "Function", (RedObject *)&FFI_FunctionType))	return 0;
	if (!RedObject_AddConst(self, "Instance", (RedObject *)&FFI_InstanceType))	return 0;

	/* built-in values */
	if (!RedObject_AddObject(self, "void"		, FFI_LoadType(&ffi_type_void		, 0)))						return 0;
	if (!RedObject_AddObject(self, "char"		, FFI_LoadType(&ffi_type_sint8		, sizeof(int8_t))))			return 0;
	if (!RedObject_AddObject(self, "uchar"		, FFI_LoadType(&ffi_type_uint8		, sizeof(uint8_t))))		return 0;
	if (!RedObject_AddObject(self, "short"		, FFI_LoadType(&ffi_type_sint16		, sizeof(int16_t))))		return 0;
	if (!RedObject_AddObject(self, "ushort"		, FFI_LoadType(&ffi_type_uint16		, sizeof(uint16_t))))		return 0;
	if (!RedObject_AddObject(self, "int"		, FFI_LoadType(&ffi_type_sint32		, sizeof(int32_t))))		return 0;
	if (!RedObject_AddObject(self, "uint"		, FFI_LoadType(&ffi_type_uint32		, sizeof(uint32_t))))		return 0;
	if (!RedObject_AddObject(self, "long"		, FFI_LoadType(&ffi_type_sint64		, sizeof(int64_t))))		return 0;
	if (!RedObject_AddObject(self, "ulong"		, FFI_LoadType(&ffi_type_uint64		, sizeof(uint64_t))))		return 0;
	if (!RedObject_AddObject(self, "float"		, FFI_LoadType(&ffi_type_float		, sizeof(float))))			return 0;
	if (!RedObject_AddObject(self, "double"		, FFI_LoadType(&ffi_type_double		, sizeof(double))))			return 0;
	if (!RedObject_AddObject(self, "long_double", FFI_LoadType(&ffi_type_longdouble	, sizeof(long double))))	return 0;
	if (!RedObject_AddObject(self, "pointer"	, FFI_LoadType(&ffi_type_pointer	, sizeof(void *))))			return 0;

	/* functions */
	if (!RedObject_AddUnary(self	, "object_as_address"	, object_as_address))	return 0;
	if (!RedObject_AddUnary(self	, "memory_as_address"	, memory_as_address))	return 0;
	if (!RedObject_AddUnary(self	, "memory_from_object"	, memory_from_object))	return 0;
	if (!RedObject_AddBinary(self	, "memory_from_address"	, memory_from_address))	return 0;

	return 1;
}
